using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;

namespace CodeDom
{
  internal class TemplateNameGenerator
  {
    private readonly CodeDomProvider _provider;
    private readonly IDictionary<Guid, String> _fieldNames = new Dictionary<Guid, String>();
    private readonly IDictionary<Guid, String> _propertyNames = new Dictionary<Guid, String>();
    private readonly IDictionary<String, IDictionary<String, String>> _namespaceTypeNames = new Dictionary<String, IDictionary<String, String>>();
    private readonly string _rootNameSpace;
    private readonly string _rootFolder;

    public TemplateNameGenerator(CodeDomProvider provider, string rootNameSpace, string rootFolder)
    {
      if (provider == null)
        throw new ArgumentNullException("provider");
      
      _provider = provider;
      _rootFolder = rootFolder;
      _rootNameSpace = rootNameSpace;
    }

    private string ValidateNameIsUnique(string name, IEnumerable<String> usedNames)
    {
      int index = 1;
      string returnValue = name;
      while (usedNames.Contains(returnValue))
      {
        returnValue = GenerateNameWithSuffix(name, index);
        index++;
      }
      return returnValue;
    }

    private string GenerateNameWithSuffix(string name, int suffixIndex)
    {
      string suffixedName = string.Format("{0}{1}", name, suffixIndex);
      return suffixedName;
    }

    private string CreateValidIdentifier(string name)
    {
      name = CorrectIdentifierCasing(name);
      name = RemoveUnsupportedChars(name);
      name = _provider.CreateValidIdentifier(name);
      return name;
    }

    private string RemoveUnsupportedChars(string name)
    {
      name = name.Trim();
      char[] unsupportedFirstChars = "0123467890".ToCharArray();
      if (unsupportedFirstChars.Contains(name[0]))
        name = "_" + name;
      return name;
    }

    private string CorrectIdentifierCasing(string name)
    {
      char[] spacingChars = new[] {'_', ' ', '-', '\t'};
      Int32 spaceIndex = name.IndexOfAny(spacingChars);
      while (spaceIndex > -1)
      {
        string subStringBeforeSpace = name.Substring(0, spaceIndex);
        string upperCasedFirstChar = name[spaceIndex + 1].ToString().ToUpperInvariant();
        string subStringAfterSpace = name.Substring(spaceIndex + 2);
        
        name = subStringBeforeSpace + upperCasedFirstChar + subStringAfterSpace;
        spaceIndex = name.IndexOfAny(spacingChars);
      }
      return name;
    }

    public string GetFieldName(TemplateField field, string owningTypeName)
    {
      if (!_fieldNames.ContainsKey(field.ID))
      {
        string name = CreateValidItemName(field);
        name = "_" + name[0].ToString().ToLower() + name.Substring(1);
        //TODO: Check only for uniqueness within the templates inheritance
        name = ValidateNameIsUnique(name, _fieldNames.Values);        
        name = GetValidNameWithinOwningType(name, owningTypeName);
        _fieldNames.Add(field.ID, name);
      }
      return _fieldNames[field.ID];
    }

    public string GetPropertyName(TemplateField field, string owningTypeName)
    {
      if (!_propertyNames.ContainsKey(field.ID))
      {
        string name = CreateValidItemName(field);
        //TODO: Check only for uniqueness within the templates inheritance
        name = ValidateNameIsUnique(name, _propertyNames.Values);
        name = GetValidNameWithinOwningType(name, owningTypeName);
        _propertyNames.Add(field.ID, name);
      }
      return _propertyNames[field.ID];
    }

    private string GetValidNameWithinOwningType(string name, string owningTypeName)
    {
      //Cannot have members with same name as their owning type
      if (owningTypeName == name)
        name = name + "Field";
      return name;
    }

    public string GetClassName(Template template)
    {
      return GetUniqueTypeName(template, String.Empty);
    }

    private string GetUniqueTypeName(Template template, string prefix)
    {
      string namespaceName = GetNamespaceName(template);
      if (!_namespaceTypeNames.ContainsKey(namespaceName))
        _namespaceTypeNames.Add(namespaceName, new Dictionary<String, String>());

      var existingNames = _namespaceTypeNames[namespaceName];

      string nameKey = template.ID + prefix;
      if (!existingNames.ContainsKey(nameKey))
      {
        string name = prefix + CreateValidItemName(template);
        name = ValidateNameIsUnique(name, existingNames.Values);
        existingNames.Add(nameKey, name);
      }
      return existingNames[nameKey];
    }

    private string CreateValidItemName(IItem template)
    {
      string name = template.Name;
      name = CreateValidIdentifier(name);
      return name;
    }

    public string GetNamespaceName(Template template)
    {
      if (template == null)
        throw new ArgumentNullException("template");
        
      String path = template.Parent.Path;
      if (path.StartsWith(_rootFolder, StringComparison.InvariantCultureIgnoreCase))
        path = path.Remove(0, _rootFolder.Length);
      var templateParts = path.Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).Select(name => CreateValidIdentifier(name));
      if (!templateParts.Any())
        return _rootNameSpace;
      return string.Format("{0}.{1}", _rootNameSpace, String.Join(".", templateParts.ToArray()));
    }

    public string GetInterfaceName(Template template)
    {
      return GetUniqueTypeName(template, "I");
    }

    public string GetFieldMethodName(TemplateField field, string owningTypeName)
    {
      string propertyName = GetPropertyName(field, owningTypeName);
      return "Get" + propertyName + "Value";
    }
  }
}